rainを導入してコマンドラインベースのCloudFormationをより簡単に、より使いやすく
今日のひとこと:止まない雨はない
はじめに
コマンドラインベースのCloudFormationの管理を便利にするrainを触り倒していきます。
rainとは
rainとは、AWS CloudFormationテンプレート(以下、テンプレート)やスタックを操作するためのコマンドラインツールです。
rainの特徴
rainの特徴は、既存のテンプレートを活用できることだと思っています。Terraform、Pulumi、AWS CDKなど、様々なソリューションがありますが、どれもIaCを再コーディングする必要があります。既存の資産を有効活用するならrainはもってこいのツールです。
rainのセットアップ
まずはrainのインストールして利用できるようにしてきます。筆者の環境はMacOS(Catalina10.15.7)です。Windowsにもインストールして利用できますがここでは割愛します。
brew insatll rain
でインストールできます。
rain -v
でバージョンが表示されればインストールOKです。
% rain -v rain v1.2.0 darwin/amd64
rainは .aws/config
、 .aws/credentials
を参照して実行します。 rain info
を実行するとcredentialsから認証情報をできます。
% rain info Account: 012345678910 Region: ap-northeast-1 Identity: arn:aws:iam::012345678910:user/iamusername
他のprofileはAWS CLI同様に、 -p, --profile
オプションを指定して実行できます。AssumeRoleで複数のAWSアカウントを操作するユーザーとしては嬉しいです。
% rain ls --profile profile-name
rainの基本操作
ls(スタック表示)
rainはCloudFormationサービスに特化したツールです。まずは以下のコマンドでスタックの一覧を表示します。
% rain ls CloudFormation stacks in ap-northeast-1: stack-name1: CREATE_COMPLETE stack-name2: CREATE_COMPLETE CloudFormation stacks in us-east-1: stack-name1: CREATE_COMPLETE
Shellを使い慣れている人には直感的にわかりやすいコマンドです。AWS CLIで同じ内容を表示しようとするとオプション含めて
% aws cloudformation describe-stacks --query 'Stacks[*].[StackName,StackStatus]' --output text
を入力する必要があります。
rain ls stack-name1
とすると指定したスタックのみ表示できます。-a, --all
オプションを指定するとスタックの詳細が表示されます。
outputsを表示してくれるところがめっちゃ良きです。
% rain ls stack-name1 Stack stack-name1: CREATE_COMPLETE Outputs: AssumeRole: arn:aws:iam::012345678910:role/AssumeRole # AssumeRoleArn % rain ls -a stack-name1 Stack stack-name1: CREATE_COMPLETE Parameters: ExternalID: e3c2d49e-903e-xxxx-xxxx-e68bbb592da9 Resources: stack-name1: CREATE_COMPLETE stack-name1 Outputs: stack-name1: arn:aws:iam::012345678910:role/stack-name1 # stack-name1Arn
fmt(テンプレートのフォーマット)
まずはスタックを作成してみます。ローカル環境にあるテンプレートを指定してスタックを作成できます。以下のサンプルテンプレートを用意しました。
AWSTemplateFormatVersion: "2010-09-09" Description: Network Template. Parameters: Env: Type: String vpcCidr: Type: String Resources: igw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${Env}-igw igwAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref igw VpcId: !Ref vpc1 vpc1: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref vpcCidr EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${Env}-vpc1 PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: "AWS::Region" CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]] VpcId: !Ref vpc1 Tags: - Key: Name Value: !Sub ${Env}-public-subnet-1 Outputs: vpc1: Value: !Ref vpc1
rainは、cfn-formatによる標準フォーマットでデプロイされます。手元のテンプレートと差分をなくす為、デプロイ前にテンプレートをフォーマットします。
% rain fmt ./template.yml
AWSTemplateFormatVersion: "2010-09-09" Description: Network Template. Parameters: Env: Type: String vpcCidr: Type: String Resources: igw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${Env}-igw MyVPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref igw VpcId: !Ref vpc1 vpc1: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref vpcCidr EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${Env}-vpc1 PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: AWS::Region CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]] VpcId: !Ref vpc1 Tags: - Key: Name Value: !Sub ${Env}-public-subnet-1 Outputs: vpc1: Value: !Ref vpc1
オプションを指定しない場合は、標準出力です。 -w, --write
オプションを指定してテンプレートを上書きできます。
% rain fmt -w ./template.yml
deploy(ローカル環境にあるテンプレートからスタック展開)
fmtしたローカルにあるテンプレートをデプロイしてきます。
--params
でParametersに代入できます。key1=Value,key2=Value,key3=Value...
ファイルの指定は、ファイルパスです。(file://~ではありません)
% rain deploy ./template.yml stack-name1 --params Env=dev,vpcCidr="10.10.10.0/16" rain needs to create an S3 bucket called 'rain-artifacts-0123456789010-ap-northeast-1'. Continue? (Y/n) Y CloudFormation will make the following changes: Stack stack-name1: + AWS::EC2::VPC vpc1 Do you wish to continue? (Y/n) Y Deploying template 'stack-name1.yml' as stack 'stack-name1' in ap-northeast-1. Stack stack-name1: CREATE_COMPLETE Outputs: vpc1: vpc-08c7ec1ca037ac008 Successfully deployed stack-name1
deployの流れは以下のとおりです。
- rain用S3バケットのチェック
- ChangeSet
- スタックのデプロイ
deployには、パッケージ化したテンプレートのアーティファクトの格納先としてrain用のS3バケットの有無がチェックされます。バケットがない場合は、 rain needs to create an S3 bucket called 'rain-artifacts-0123456789010-ap-northeast-1'. Continue? (Y/n)
が出力されるのでS3バケットを作成します。ChangeSetを確認し、スタックをデプロイします。 Successfully deployed stack-name1
が表示されればデプロイの成功です。
なお、(Y/n)の入力は、Shellでもよく見かける -y, --yes
オプションを指定すれば省略されます。
スタックの作成またはアップデートが失敗すると、エラー対象のLogicalIDと理由が出力されます。template.ymlのPublicSubnet1を一部を修正してデプロイしてみます。
PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - - 0 + - 3 - !GetAZs Ref: AWS::Region CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]] VpcId: !Ref vpc1 Tags: - Key: Name Value: !Sub ${Env}-public-subnet-1
% rain deploy -y ./template.yml stack-name1 --params Env="dev",vpcCidr="10.10.0.0/16" Deploying template 'template.yml' as stack 'stack-name1' in ap-northeast-1. Stack stack-name1: UPDATE_ROLLBACK_COMPLETE Outputs: vpc1: vpc-0ab94716b91314657 Messages: - PublicSubnet1 - subnet-003e0bd623f3a0a12: Template error: Fn::Select cannot select nonexistent value at index 3 failed deploying stack 'stack-name1'
デプロイエラーのLogicalIDだけ表示されるので確認しやすいです。ちなみに「3.スタックのデプロイ」で新規デプロイの場合は、スタックの削除までやってくれます。
logs(スタックのイベントログを出力)
スタックのイベント履歴を確認できます。主にのエラーイベント(UPDATE_FAILEDなど)を出力します。
% rain logs stack-name1 No interesting log messages to display. To see everything, use the --all flag
上記にも出力されている通り、エラーイベントなどがなければ出力されません。すべてのイベント履歴を表示するには、 -a, --all
オプションを指定します。
% rain logs stack-name1 --all Aug 31 12:30:23 stack-name1/vpc1 (AWS::EC2::VPC) CREATE_COMPLETE Aug 31 12:30:07 stack-name1/vpc1 (AWS::EC2::VPC) CREATE_IN_PROGRESS "Resource creation Initiated" Aug 31 12:30:06 stack-name1/vpc1 (AWS::EC2::VPC) CREATE_IN_PROGRESS ~
rm(スタックの削除)
デプロイしたスタックを削除します。
% rain rm stack-name1 Stack stack-name1: CREATE_COMPLETE Are you sure you want to delete this stack? (y/N) y Successfully deleted stack 'stack-name1'
(Y/n)の入力は、deployコマンドと同様に -y, --yes
オプションを指定すれば省略されます。
cat(スタックのテンプレート取得)
デプロイされているスタックのテンプレートを出力します。デフォルトフォーマットされた形式で出力されます。
% rain cat stack-name1
AWSTemplateFormatVersion: "2010-09-09" Description: Network Template. Parameters: Env: Type: String vpcCidr: Type: String Resources: igw: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${Env}-igw MyVPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref igw VpcId: !Ref vpc1 vpc1: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref vpcCidr EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${Env}-vpc1 PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Select - 0 - !GetAZs Ref: AWS::Region CidrBlock: !Select [0, !Cidr [!GetAtt vpc1.CidrBlock, 2, 8]] VpcId: !Ref vpc1 Tags: - Key: Name Value: !Sub ${Env}-public-subnet-1 Outputs: vpc1: Value: !Ref vpc1
フォーマットせずにスタックからテンプレートを出力する場合は、-u, --unformatted
オプションを指定します。
diff(テンプレートの比較)
ローカル環境にあるテンプレートを比較します。セクション(Resources,Parameters,Outputsなど)やLogicalID、Propertiesの差分を確認できます。
- セクション差分(Outputsなし)
% rain diff template.yml template1.yml (-) Outputs: {...}
- LogicalID差分(ResourcesセクションのPublicSbunet1なし)
% rain diff template.yml template1.yml (|) Resources: (-) PublicSubnet1: {...}
- Propeties差分(Tagsプロパティなし)
% rain diff template.yml template1.yml (|) Resources: (|) PublicSubnet1: (|) Properties: (-) Tags: [...]
- Propeties値差分
% rain diff template.yml template1.yml (|) Resources: (|) PublicSubnet1: (|) Properties: (|) Tags: (|) [0]: (|) Value: (>) Fn::Sub: ${Env}-public-subnet-2
catおよびdiffコマンドの用途は、スタックのテンプレートとローカル環境にある修正したテンプレートの差分チェックというところでしょうか。
tree(リソース間依存関係の確認)
テンプレート内の各リソースの依存関係(DependsOn)を確認できます。
% rain tree template.yml Resources: igw: DependsOn: Parameters: - Env vpc1: DependsOn: Parameters: - Env - vpcCidr MyVPCGatewayAttachment: DependsOn: Resources: - igw - vpc1 PublicSubnet1: DependsOn: Parameters: - AWS::Region - Env Resources: - vpc1 Outputs: vpc1: DependsOn: Resources: - vpc1
igw(LogicalID))だと、ParametersのEnvが依存関係にあります。MyVPCGatewayAttachment(LogicalID)では、Resourcesのigw、vpc1が依存関係にあります。このような形で確認できるのは、めっちゃ良きです。
build(テンプレートの作成)
テンプレートを作成するには、AWSドキュメントを見ながらコーディングしていく必要があります。必須のプロパティだったり書き方を理解するのに時間がかかるものですが、 rain build
であっという間にテンプレートが生成されます。template.ymlと同様のリソースを指定してrainでテンプレートを生成してみます。
% rain build "AWS::EC2::InternetGateway" "AWS::EC2::VPCGatewayAttachment" "AWS::EC2::VPC" "AWS::EC2::Subnet"
AWSTemplateFormatVersion: "2010-09-09" Description: Template generated by rain Resources: MyInternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: CHANGEME Value: CHANGEME MySubnet: Type: AWS::EC2::Subnet Properties: AssignIpv6AddressOnCreation: false # Optional AvailabilityZone: CHANGEME # Optional CidrBlock: CHANGEME Ipv6CidrBlock: CHANGEME # Optional MapPublicIpOnLaunch: false # Optional OutpostArn: CHANGEME # Optional Tags: - Key: CHANGEME Value: CHANGEME VpcId: CHANGEME MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: CHANGEME EnableDnsHostnames: false # Optional EnableDnsSupport: false # Optional InstanceTenancy: CHANGEME # Optional Tags: - Key: CHANGEME Value: CHANGEME MyVPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: CHANGEME # Optional VpcId: CHANGEME VpnGatewayId: CHANGEME # Optional Outputs: MySubnetAvailabilityZone: Value: !GetAtt MySubnet.AvailabilityZone MySubnetIpv6CidrBlocks: Value: !GetAtt MySubnet.Ipv6CidrBlocks MySubnetNetworkAclAssociationId: Value: !GetAtt MySubnet.NetworkAclAssociationId MySubnetOutpostArn: Value: !GetAtt MySubnet.OutpostArn MySubnetVpcId: Value: !GetAtt MySubnet.VpcId MyVPCCidrBlock: Value: !GetAtt MyVPC.CidrBlock MyVPCCidrBlockAssociations: Value: !GetAtt MyVPC.CidrBlockAssociations MyVPCDefaultNetworkAcl: Value: !GetAtt MyVPC.DefaultNetworkAcl MyVPCDefaultSecurityGroup: Value: !GetAtt MyVPC.DefaultSecurityGroup MyVPCIpv6CidrBlocks: Value: !GetAtt MyVPC.Ipv6CidrBlocks
リソースタイプを指定するだけであっという間にテンプレートが生成されます。 CHANGEME
(# Optional無し)は必須のプロパティ、 CHANGEME # Optional
は任意のプロパティです。必要に応じて値を設定します。
なお、最低限のプロパティだけ生成する場合は、-b, --bare
オプションを指定します。
% rain build -b "AWS::EC2::InternetGateway" "AWS::EC2::VPCGatewayAttachment" "AWS::EC2::VPC" "AWS::EC2::Subnet"AWSTemplateFormatVersion:
"2010-09-09" Description: Template generated by rain Resources: MyInternetGateway: Type: AWS::EC2::InternetGateway Properties: {} MySubnet: Type: AWS::EC2::Subnet Properties: CidrBlock: CHANGEME VpcId: CHANGEME MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: CHANGEME MyVPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: CHANGEME
また、リソースタイプは複数指定も可能です。以下、AWS::EC2::Subnet
を複数指定して生成できます。
% rain build -b "AWS::EC2::InternetGateway" "AWS::EC2::VPCGatewayAttachment" "AWS::EC2::VPC" "AWS::EC2::Subnet" "AWS::EC2::Subnet" "AWS::EC2::Subnet" "AWS::EC2::Subnet"
AWSTemplateFormatVersion: "2010-09-09" Description: Template generated by rain Resources: MyInternetGateway: Type: AWS::EC2::InternetGateway Properties: {} MySubnet1: Type: AWS::EC2::Subnet Properties: CidrBlock: CHANGEME VpcId: CHANGEME MySubnet2: Type: AWS::EC2::Subnet Properties: CidrBlock: CHANGEME VpcId: CHANGEME MySubnet3: Type: AWS::EC2::Subnet Properties: CidrBlock: CHANGEME VpcId: CHANGEME MySubnet4: Type: AWS::EC2::Subnet Properties: CidrBlock: CHANGEME VpcId: CHANGEME MyVPC: Type: AWS::EC2::VPC Properties: CidrBlock: CHANGEME MyVPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: CHANGEME
ちなみに -l, --list
で指定できるリソースタイプを確認できます。v1.2.0だと 740 のリソースタイプが指定できます。
buildコマンドでベースのテンプレートを生成して、ParametersやOutputsセクションなど追加コーディングしていきましょう。
ファーストインプレッションを終えて
Shellに慣れている人間には直感的に扱いやすいです。コマンドオプションも多くなく、簡単な教育で現場に浸透させやすいツールだと感じます。
面倒な部分として -p, --profile
オプションを指定するとrainコマンドの都度、MFA Tokenの入力が求められます。
ソースコードを見ると WithAssumeRoleCredentialOptionsによって以前の一時資格情報が上書きされる為です。
stsやdirenvなどでprofileオプションを指定しない方法が扱いやすいです。
個人的にはコマンドラインによるCloudFormataion管理で、rainの右に出るものは現時点でないと思っています。次回は、rainを使ったテンプレートのCI/CDパイプラインの実装を検証していきたいと思います。